{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 生成随机数字\n",
    "tensorflow 在 [tf.random](https://tensorflow.google.cn/api_docs/python/tf/random) 模型中提供了一组伪随机数生成器（pesudo-random number generation——RNG）。这篇文章介绍了你能如何控制随机数生成器，以及这些生成器如何与其它的 tensorflow 子系统相互影响。\n",
    "\n",
    "tensorflow 为控制随机数生成过程提供了两个方法：\n",
    "1. 通过显式使用 tf.random.Generator 对象。每个这样的对象都维护一个状态（在 tf.Variable 中），该状态将在每次生成数字后更改。\n",
    "2. 通过纯功能的无状态随机函数，如 [tf.random.stateless_uniform](https://tensorflow.google.cn/api_docs/python/tf/random/stateless_uniform)。使用相同的参数（包括种子（seed））在相同的设备上调用这些函数将始终产生相同的结果。\n",
    "\n",
    "> 警告：在 tensorflow1 中的过去式 RNG 里。像 [tf.random.uniform](https://tensorflow.google.cn/api_docs/python/tf/random/uniform) 和 [tf.random.normal](https://tensorflow.google.cn/api_docs/python/tf/random/normal) 还没有被弃用，但是强烈建议不要使用。\n",
    "\n",
    "> 警告：随机数不能保证在 tensorflow 版本之间是一致的，详细可查看 [Version Compatibilit](https://tensorflow.google.cn/guide/versions#what_is_not_covered)。\n",
    "\n",
    "## 起步"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "# 创建2个虚拟设备 cpu:0 和 cpu:1，用于使用分发策略\n",
    "physical_devices = tf.config.experimental.list_physical_devices(\"CPU\")\n",
    "tf.config.experimental.set_virtual_device_configuration(\n",
    "    physical_devices[0], [\n",
    "        tf.config.experimental.VirtualDeviceConfiguration(),\n",
    "        tf.config.experimental.VirtualDeviceConfiguration()\n",
    "    ])\n",
    "print(tf.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## tf.random.experimental.Generator 类\n",
    "tf.random.experimental.Generator 类被用于让每个 RNG 产生不同的结果。它维持了一个内部状态（被 tf.Variable 对象所管理），每次生成随机数都会更新一次。因为这个状态被 tf.Variable 所管理，所以它拥有 tf.Variable 提供的所有功能，如简单的检查点设置、自动控制依赖性和线程安全性。\n",
    "\n",
    "你可以通过手动创建一个类对象获得 tf.random.experimental.Generator 或者调用 tf.random.experimental.get_global_generator() 获得默认的全局生成器。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g1 = tf.random.experimental.Generator.from_seed(1)\n",
    "print(g1.normal(shape=[2, 3]))\n",
    "g2 = tf.random.experimental.get_global_generator()\n",
    "print(g2.normal(shape=[2, 3]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "创建生成器对象有多种方法。最简单的是上面所示的 [Generator.from_seed](https://tensorflow.google.cn/api_docs/python/tf/random/experimental/Generator#from_seed)，它从一个种子创建一个生成器。种子是任何非负整数。from_seed 还接受一个可选参数 alg，它是将被这个生成器使用的 RNG 算法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g1 = tf.random.experimental.Generator.from_seed(1, alg='philox')\n",
    "print(g1.normal(shape=[2, 3]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有关它的更多信息，请参阅下面的算法部分。\n",
    "\n",
    "另一种创建生成器的方法是使用 [Generator.from_non_deterministic_state](https://tensorflow.google.cn/api_docs/python/tf/random/experimental/Generator#from_non_deterministic_state)。以这种方式创建的生成器将从一个不确定的状态开始，具体取决于时间和操作系统。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g = tf.random.experimental.Generator.from_non_deterministic_state()\n",
    "print(g.normal(shape=[2, 3]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "还有其他创建生成器的方法，例如从显式状态创建生成器，本指南不包括这些方法。\n",
    "\n",
    "使用 tf.random.experimental.get_global_generator 获取全局生成器时，需要小心设备放置。全局生成器在第一次调用 tf.random.experimental.get_global_generator 时创建（从非确定状态），并在该调用时放置在默认设备上。因此，例如，如果你调用 tf.random.experimental.get_global_generator 的第一个站点位于 tf.device(\"gpu\") 范围内，则全局生成器将放置在 GPU 上，稍后从 CPU 使用全局生成器将产生 GPU 到CPU的副本。\n",
    "\n",
    "还有一个函数 tf.random.experimental.set_global_generator 用于用另一个生成器对象替换全局生成器。应该谨慎使用此函数，因为旧的全局生成器可能已被 [tf.function](https://tensorflow.google.cn/api_docs/python/tf/function) 捕获（作为弱引用），替换它将导致垃圾收集，从而破坏 tf.function。重置全局生成器的更好方法是使用 \"重置\" 函数之一，例如[Generator.reset_from_seed](https://tensorflow.google.cn/api_docs/python/tf/random/experimental/Generator#reset_from_seed)，它不会创建新的生成器对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g = tf.random.experimental.Generator.from_seed(1)\n",
    "print(g.normal([]))\n",
    "print(g.normal([]))\n",
    "g.reset_from_seed(1)\n",
    "print(g.normal([]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 创建独立的随机数流\n",
    "在许多应用程序中，我们需要多个独立的随机数流，因为它们不会重叠，也没有任何统计上可检测到的相关性。这是通过使用生成器实现的。拆分以创建多个保证彼此独立的生成器（即生成独立的流）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g = tf.random.experimental.Generator.from_seed(1)\n",
    "print(g.normal([]))\n",
    "new_gs = g.split(3)\n",
    "for new_g in new_gs:\n",
    "  print(new_g.normal([]))\n",
    "print(g.normal([]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "split 将更改调用它的生成器的状态（在上面的示例中为 g），类似于 normal 等 RNG 方法。除了相互独立之外，新生成器（new_gs）还保证独立于旧生成器（g）。\n",
    "\n",
    "当你想要确保使用的生成器与其他计算位于相同的设备上时，生成新的生成器也很有用，以避免跨设备复制的开销。例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.device(\"cpu\"):  # 将 \"cpu\" 更改为你想要的设备\n",
    "  g = tf.random.experimental.get_global_generator().split(1)[0]  \n",
    "  print(g.normal([]))  # 与全局生成器不同，使用 g 不会导致跨设备复制"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 注意：理论上，你可以使用 from_seed 这样的构造函数来获得一个新生成器，而不是在这里使用split，但是这样做就不能保证新生成器独立于全局生成器。你还将面临这样的风险：你可能会不小心创建两个具有相同种子的生成器，或者使用导致随机数流重叠的种子。\n",
    "\n",
    "你可以递归地调用 split，在 split 的生成器上调用 split。对于递归的深度没有限制（除非是整数溢出）。\n",
    "\n",
    "### 与 tf.function 相互作用\n",
    "tf.random.experimental.Generator 遵循与 tf.Variable 相同的规则。与 tf.function 一起使用时为变量。这包括三个方面。\n",
    "#### 在 tf.function 外部创建生成器\n",
    " tf.function 函数可以使用外部创建的生成器。\n",
    " ```python\n",
    "g = tf.random.experimental.Generator.from_seed(1)\n",
    "@tf.function\n",
    "def foo():\n",
    "  return g.normal([])\n",
    "print(foo())\n",
    "```\n",
    "用户需要确保在调用函数时生成器对象仍然是活动的（而不是被垃圾收集的）。\n",
    "\n",
    "#### 在 tf.function 内部创建生成器\n",
    "在 tf.function 中创建生成器。函数只能在第一次运行时发生：\n",
    "```python\n",
    "g = None\n",
    "@tf.function\n",
    "def foo():\n",
    "  global g\n",
    "  if g is None:\n",
    "    g = tf.random.experimental.Generator.from_seed(1)\n",
    "  return g.normal([])\n",
    "print(foo())\n",
    "print(foo())\n",
    "```\n",
    "\n",
    "#### 将生成器作为参数传递给 tf.function\n",
    "当用作 tf.function 的参数时。函数中，具有相同状态大小（状态大小由 RNG 算法决定）的不同生成器对象不会导致 tf.function 的重跟踪，而具有不同状态大小的生成器对象则会导致 tf.function 的重新跟踪。\n",
    "```python\n",
    "num_traces = 0\n",
    "@tf.function\n",
    "def foo(g):\n",
    "  global num_traces\n",
    "  num_traces += 1\n",
    "  return g.normal([])\n",
    "foo(tf.random.experimental.Generator.from_seed(1))\n",
    "foo(tf.random.experimental.Generator.from_seed(2))\n",
    "print(num_traces)\n",
    "```\n",
    "\n",
    "### 与分发策略相互作用\n",
    "生成器与分发策略交互的方式有三种。\n",
    "#### 在分发策略外部创建生成器\n",
    "如果在策略作用域之外创建生成器，那么所有副本对生成器的访问都将被序列化，因此副本将获得不同的随机数。\n",
    "```python\n",
    "g = tf.random.experimental.Generator.from_seed(1)\n",
    "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
    "with strat.scope():\n",
    "  def f():\n",
    "    print(g.normal([]))\n",
    "  results = strat.run(f)\n",
    "```\n",
    "> 注意：这种使用可能会有性能问题，因为生成器的设备与副本不同。\n",
    "\n",
    "#### 在分发策略内部创建生成器\n",
    "不允许在策略范围内创建生成器，因为在如何复制生成器方面存在不确定性（例如，是否应该复制生成器以使每个副本获得相同的随机数，或 \"split\" 生成器以使每个副本获得不同的随机数）。\n",
    "```python\n",
    "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
    "with strat.scope():\n",
    "  try:\n",
    "    tf.random.experimental.Generator.from_seed(1)\n",
    "  except ValueError as e:\n",
    "    print(\"ValueError:\", e)\n",
    "```\n",
    "注意 Strategy.run 将在一个策略范围内隐式地运行它的参数函数：\n",
    "```python\n",
    "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
    "def f():\n",
    "  tf.random.experimental.Generator.from_seed(1)\n",
    "try:\n",
    "  strat.run(f)\n",
    "except ValueError as e:\n",
    "  print(\"ValueError:\", e)\n",
    "```\n",
    "#### 将生成器作为参数传递给 Strategy.run\n",
    "如果你星耀每个副本使用自己的生成器，你需要生成 n 个生成器（通过复制或分割），其中 n 是副本的数量，然后将它们作为参数传递给 Strategy.run。\n",
    "```python\n",
    "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
    "gs = tf.random.experimental.get_global_generator().split(2)\n",
    "# to_args 是一种解决缺少为 run 创建参数的 API 的问题的方法。当这些 API 可用时，它将被替换。\n",
    "def to_args(gs):  \n",
    "  with strat.scope():\n",
    "    def f():\n",
    "      return [gs[tf.distribute.get_replica_context().replica_id_in_sync_group]]\n",
    "    return strat.run(f)\n",
    "args = to_args(gs)\n",
    "def f(g):\n",
    "  print(g.normal([]))\n",
    "results = strat.run(f, args=args)\n",
    "```\n",
    "\n",
    "## 无状态的随机数生成器\n",
    "无状态 RNG 的使用很简单。由于它们只是纯函数，所以不涉及任何状态或副作用。\n",
    "```python\n",
    "print(tf.random.experimental.stateless_normal(shape=[2, 3], seed=[1, 2]))\n",
    "print(tf.random.experimental.stateless_normal(shape=[2, 3], seed=[1, 2]))\n",
    "```\n",
    "每个无状态 RNG 都需要一个种子参数，它需要是一个形状为 [2] 的整数张量。op 的结果完全由这个种子决定。\n",
    "\n",
    "## 算法\n",
    "### 通用\n",
    "在所有设备上，tf.random.experimental.Generator 类和无状态函数都支持 Philox 算法（编写为 \"Philox\" 或tf.random.algorithm.Philox）。\n",
    "\n",
    "如果使用相同的算法并从相同的状态开始，不同的设备将生成相同的整数。它们还将生成 \"几乎相同\" 的浮点数，尽管由于设备执行浮点计算的方式不同（例如还原顺序），可能会导致数字上的小差异。\n",
    "\n",
    "### XLA 设备\n",
    "在XLA驱动的设备上（如TPU，当启用 XLA 时也支持 CPU/GPU），也支持 ThreeFry 算法（写作 \"ThreeFry\" 或 tf.random.Algorithm.THREEFRY）。与 Philox 相比，该算法在TPU上运行速度较快，但在CPU/GPU 上运行速度较慢。\n",
    "\n",
    "有关这些算法的详细信息，参阅 [Parallel Random Numbers: As Easy as 1, 2, 3](https://www.thesalmons.org/john/random123/papers/random123sc11.pdf)。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6rc1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
